﻿using System;
using System.Windows.Media;
using Microsoft.Linq.Expressions;
using System.Reflection;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using Bling.DSL;
using Bling.Util;
using Bling.Properties;


namespace Bling.Linq {
  public class ParameterContext {
    public readonly List<ParameterExpr> Slots = new List<ParameterExpr>();
    private readonly Dictionary<ParameterExpr, ParameterExpression> Expressions = new Dictionary<ParameterExpr, ParameterExpression>();
    public ParameterExpression this[ParameterExpr value] {
      get {
        if (!Expressions.ContainsKey(value))
          Expressions[value] = Expression.Parameter(value.TypeOfT, value.Name);
        return Expressions[value];
      }

    }
    public ParameterExpression[] Flush() {
      var ret = new ParameterExpression[Slots.Count];
      for (int i = 0; i < Slots.Count; i++) {
        ret[i] = this[Slots[i]];
      }
      return ret;
    }

    public ParameterExpr<T> NewParameter<T>() {
      var ret = new ParameterExpr<T>();
      Slots.Add(ret);
      return ret;
    }
    public void Add<T>(ParameterExpr<T> arg) {
      Slots.Add(arg);
    }
  }
  public class LinqValue<T> : Expr<T> {
    public readonly Expression Expression;
    public LinqValue(Expression Expression) {
      this.Expression = Expression;
    }
    public override string ToString() {
      return Expression.ToString();
    }
    public override Eval<EVAL>.Info<T> Eval<EVAL>(Eval<EVAL> txt) {
      if (txt is LinqEval<EVAL>) {
        var ret = new LinqEval<EVAL>.MyInfo<T>() { Value = Expression };
        return (Eval<EVAL>.Info<T>)(object)ret;
      }
      throw new NotImplementedException();
    }
  }

  public class DefaultLinqEval : LinqEval<DefaultLinqEval> {
    public DefaultLinqEval(ParameterContext txt) : base(txt) { }
    public static Func<T> Eval<T>(Expr<T> value) {
      var f = (new DefaultLinqEval(new ParameterContext())).Eval<T, Microsoft.Func<T>>(value);
      return () => f();
    }
    public static Func<S, T> Eval<S, T>(Func<Expr<S>, Expr<T>> value) {
      var slots = new ParameterContext();
      var slot = slots.NewParameter<S>();
      var f = (new DefaultLinqEval(slots)).Eval<T, Microsoft.Func<S, T>>(value(slot));
      return (e1) => f(e1);
    }
    public static Func<S, T, U> Eval<S, T, U>(Func<Expr<S>, Expr<T>, Expr<U>> value) {
      var slots = new ParameterContext();
      var slot1 = slots.NewParameter<S>();
      var slot2 = slots.NewParameter<T>();
      var f = (new DefaultLinqEval(slots)).Eval<U, Microsoft.Func<S, T, U>>(value(slot1, slot2));
      return (e1, e2) => f(e1, e2);
    }
    public static Func<S, T, U, V> Eval<S, T, U, V>(Func<Expr<S>, Expr<T>, Expr<U>, Expr<V>> value) {
      var slots = new ParameterContext();
      var slot1 = slots.NewParameter<S>();
      var slot2 = slots.NewParameter<T>();
      var slot3 = slots.NewParameter<U>();
      return (new DefaultLinqEval(slots)).Eval<V, Func<S, T, U, V>>(value(slot1, slot2, slot3));
    }
    public static Func<S, T, U, V, W> Eval<S, T, U, V, W>(Func<Expr<S>, Expr<T>, Expr<U>, Expr<V>, Expr<W>> value) {
      var slots = new ParameterContext();
      var slot1 = slots.NewParameter<S>();
      var slot2 = slots.NewParameter<T>();
      var slot3 = slots.NewParameter<U>();
      var slot4 = slots.NewParameter<V>();
      return (new DefaultLinqEval(slots)).Eval<W, Func<S, T, U, V, W>>(value(slot1, slot2, slot3, slot4));
    }
  }
  class Scope {
    internal static int Total = 0;
    internal Scope Previous;
    internal readonly int Depth = Total++;
    internal readonly Dictionary<Expr, ParameterExpression> Temps = new Dictionary<Expr, ParameterExpression>();
    internal readonly List<Expression> Instructions = new List<Expression>();
    internal ParameterExpression MakeTemp(Expr value, Func<Expression> F) {
      return MakeTempB(value, (p) => Expression.Assign(p, F()));
    }
    private ParameterExpression Find(Expr value) {
      if (Temps.ContainsKey(value)) return Temps[value];
      else if (Previous != null) return Previous.Find(value);
      else return null;
    }
    internal ParameterExpression MakeTempB(Expr value, Func<ParameterExpression, Expression> F) {
      var found = Find(value);
      if (found != null) return found;
      var p = Expression.Parameter(value.TypeOfT, "t" + Temps.Count + "_" + Depth);
      Temps[value] = p;
      var e = F(p);
      Instructions.Add(e);
      return p;
    }
  }


  // TODO: add support for conditions. 
  public abstract class LinqEval<EVAL> : Eval<EVAL>, DependencyPropertyEval<EVAL>, ForceEval, TableEval<EVAL>, ParameterEval<EVAL> where EVAL : Eval<EVAL> {
    public Info<T> Parameter<T>(ParameterExpr<T> value) {
      return new MyInfo<T>() { Value = Parameters[value] };
    }


    private Scope CurrentScope = new Scope();
    public void PushScope() {
      var scope = new Scope() { Previous = CurrentScope };
      CurrentScope = scope;
    }
    public Expression PopScope() {
      var at = CurrentScope;
      CurrentScope = at.Previous;
      if (at.Instructions.Count == 0) {
        return Expression.Empty();
      } else return Expression.Block(at.Temps.Values, at.Instructions);
    }
    public Expression PopScope<T>(Expression e) {
      var at = CurrentScope;
      CurrentScope = at.Previous;
      if (at.Instructions.Count == 0) return e;
      at.Instructions.Add(e);
      return Expression.Block(typeof(T), at.Temps.Values, at.Instructions);
    }
    public Expression PopScope(Expression e) {
      var at = CurrentScope;
      CurrentScope = at.Previous;
      if (at.Instructions.Count == 0) return e;
      at.Instructions.Add(e);
      return Expression.Block(at.Temps.Values, at.Instructions);
    }
    public List<Expression> Instructions { get { return CurrentScope.Instructions; } }

    //internal readonly Dictionary<Value, ParameterExpression> Temps = new Dictionary<Value,ParameterExpression>();
    //public readonly List<Expression> Instructions = new List<Expression>();
    //protected readonly ParameterContext Slots;
    //protected readonly List<ParameterExpression> Arguments0 = new List<ParameterExpression>();


    public TDelegate Eval<T,TDelegate>(Expr<T> value) {
      var result = ((MyInfo<T>)value.Eval(this)).Value;
      return Eval<TDelegate>(result);
    }

    /*
    public Expression Flush() {
      (Instructions.Count > 0).Assert();
      var result = Expression.Block(Expression.Block(Instructions));
      result = Expression.Block(Temps.Values, result);
      Instructions.Clear();
      Temps.Clear();
      return result;
    }
    */
    public Expression For(string nm, int count, Func<MyInfo<int>, Expression> Body) {
      var loopVar = Expression.Parameter(typeof(int), nm);
      PushScope();
      var body = Body(new MyInfo<int>() { Value = loopVar });
      Instructions.Add(body);
      body = PopScope();

      var updateVar = Expression.Assign(loopVar, Expression.Add(loopVar, Expression.Constant(1)));
      var breakLabel = Expression.Label("BREAK_" + nm);
      var checkBreak = Expression.IfThen(Expression.Equal(loopVar, Expression.Constant(count)), Expression.Break(breakLabel));
      var loop = Expression.Loop(Expression.Block(body, updateVar, checkBreak), breakLabel);
      return Expression.Block(new ParameterExpression[] { loopVar },
        Expression.Assign(loopVar, Expression.Constant(0)),
        loop);
    }

    public TDelegate Eval<TDelegate>(Expression result) {
      return Eval0<TDelegate>(result).Compile();
    }
    public Expression<TDelegate> Eval0<TDelegate>(Expression result) {
      CurrentScope.Instructions.Add(result);
      var ret0 = Expression.Lambda<TDelegate>(PopScope(), Parameters.Flush());
      (CurrentScope == null).Assert();
      return ret0;
    }
    public static Func<T> Eval<T>(Expr<T> value, Func<ParameterContext, EVAL> MakeEval) {
      var f = ((LinqEval<EVAL>)(object)MakeEval(new ParameterContext())).Eval<T, Microsoft.Func<T>>(value);
      return () => f();
    }
    public virtual Info<T> PropertyValue<T>(DependencyPropertyValue<T> value) {
      value.Target.GetValue(value.ReadProperty);

      var call = Expression.Call(Expression.Convert(((MyInfo) value.BaseTargetValue.BaseEval(this)).BaseValue, typeof(System.Windows.DependencyObject)),
                                 "GetValue", new Type[] { },
                                 Expression.Constant(value.ReadProperty, typeof(System.Windows.DependencyProperty)));
      var convert = Expression.Convert(call, typeof(T));
      return new MyInfo<T>() { Value = convert };
    }
    public virtual Info<S> PropertyValue<R, S>(Info<R> container, System.Windows.DependencyProperty P) where R : System.Windows.DependencyObject {
      var call = Expression.Call(Expression.Convert(((MyInfo<R>) container).Value, typeof(System.Windows.DependencyObject)),
                                 "GetValue", new Type[] { },
                                 Expression.Constant(P, typeof(System.Windows.DependencyProperty)));
      var convert = Expression.Convert(call, typeof(S));
      return new MyInfo<S>() { Value = convert };
    }


    public Info<T> Table<T>(TableExpr<T> value) {
      return new MyInfo<T>() {
        Value = MakeTempB(value, (temp) => {
          var cases = new SwitchCase[value.Values.Length];
          for (int i = 0; i < value.Values.Length; i++) {
            PushScope();
            var e = ((MyInfo<T>)value.Values[i].Eval(this)).Value;
            Instructions.Add(Expression.Assign(temp, e));
            cases[i] = Expression.SwitchCase(PopScope(), Expression.Constant(i));
          }
          var idx = ((MyInfo<int>)value.Index.Eval(this)).Value;
          //Expression defaultBody = Expression.Throw(Expression.New(typeof(Exception)), typeof(T));
          Expression defaultBody = Expression.Constant(default(T), typeof(T));
          return Expression.Switch(idx, defaultBody, cases);
        }),
      };
    }
    public Info<T> Block<T>(T[] Values, Info<int> idx) {
      var idx0 = ((MyInfo<int>)idx).Value;
      if (idx0 is ConstantExpression) {
        var idx1 = (int)((ConstantExpression)idx0).Value;
        return new MyInfo<T>() { Value = Expression.Constant(Values[idx1], typeof(T)) };
      } else return new MyInfo<T>() {
        Value = Expression.ArrayAccess(Expression.Constant(Values, typeof(T[])), idx0),
      };
    }


    public ParameterExpression MakeTemp(Expr value, Func<Expression> F) {
      return CurrentScope.MakeTemp(value, F);
    }
    private ParameterExpression MakeTempB(Expr value, Func<ParameterExpression, Expression> F) {
      return CurrentScope.MakeTempB(value, F);
    }
    public ParameterContext Parameters;
    private LinqEval(LinqEval<EVAL> old) {
      this.Parameters = old.Parameters;
    }
    /*
    protected LinqEval(params ParameterExpression[] arguments) {
      foreach (var a in arguments) this.Arguments0.Add(a);
    }*/
    public void SetScope<EVAL0>(LinqEval<EVAL0> other) where EVAL0 : Eval<EVAL0> {
      CurrentScope = other.CurrentScope;
    }


    public LinqEval(ParameterContext Slots) {
      //this.Slots = Slots;
      this.Parameters = Slots;
      /*
      Arguments = new Dictionary<Value, ParameterExpression>();
      if (Slots != null) foreach (var p in Slots.Slots) {
        this.Arguments[p] = Expression.Parameter(p.TypeOfT, p.ToString());
        this.Arguments0.Add(this.Arguments[p]);
      }*/
    }
    public override Info<T> Constant<T>(Constant<T> constant) {
      return new MyInfo<T>() { Value = Expression.Constant(constant.Value, typeof(T)) };
    }
    public override Info<T> ArityX<T, ARITY>(ArityInfo<T, ARITY>.ArityExpr value) {
      Expression[] values = new Expression[value.Values.Count];
      Type[] argTypes = new Type[value.Values.Count];
      for (int i = 0; i < values.Length; i++) {
        values[i] =
          (((MyInfo<double>)value.Values[i].Eval(this)).Value);
        argTypes[i] = typeof(double);
      }
      var result = new MyInfo<T>();
      if (typeof(T) == typeof(Color)) {
        for (int i = 0; i < values.Length; i++)
          values[i] = Ensure<float>(values[i]);

        result.Value = Expression.Call(typeof(Color), "FromScRgb", new Type[] { }, values);
      } else if (typeof(T) == typeof(double)) {
        result.Value = values[0];
      } else {
        var c = typeof(T).GetConstructor(argTypes);
        result.Value = Expression.New(c, values);
      }
      return result;
    }
    private Expression Ensure<T>(Expression e) {
      if (e.Type != typeof(T)) return Expression.Convert(e, typeof(T));
      else return e;
    }

    public override Info<T> Operate<S, T>(Operation<S, T> op) {
      Expression v0 = ((MyInfo<S>)op.Value.Eval(this)).Value;
      return new MyInfo<T>() { // no temping for unaries.
        Value = Ensure<T>(op.Op.Linq(v0))
      };
    }
    public override Info<U> Operate<S, T, U>(Operation<S, T, U> op) {
      return new MyInfo<U>() {
        Value = MakeTemp(op, () => {
          Expression v1 = ((MyInfo<S>)op.Value1.Eval(this)).Value;
          Expression v2 = ((MyInfo<T>)op.Value2.Eval(this)).Value;
          return Ensure<U>(op.Op.Linq(v1,v2));
        }),
      };
    }
    public override Info<V> Operate<S, T, U, V>(Operation<S, T, U, V> op) {
      return new MyInfo<V>() {
        Value = MakeTemp(op, () => {
          Expression v1 = ((MyInfo<S>)op.Value1.Eval(this)).Value;
          Expression v2 = ((MyInfo<T>)op.Value2.Eval(this)).Value;
          Expression v3 = ((MyInfo<U>)op.Value3.Eval(this)).Value;
          return Ensure<V>(op.Op.Linq(v1, v2, v3));
        }),
      };
    }
    public object Force<T>(Func<T> F) {
      return new MyInfo<T>() { Value = Expression.Invoke(Expression.Constant(F)) };
    }
    public object Force<T, S>(Expr<T> index, Func<T, S> F) {
      Expression G = ((MyInfo<T>)index.Eval(this)).Value;
      return new MyInfo<S>() { Value = Expression.Invoke(Expression.Constant(F), G) };
    }
    public interface MyInfo {
      Expression BaseValue { get; }
    }

    public class MyInfo<T> : Info<T>, MyInfo {
      public Expression Value;
      public Expression BaseValue { get { return Value; } }
    }
    public override Info<S> UpCast<T, S>(Info<T> info) {
      return new MyInfo<S>() {
        Value = Expression.Convert(((MyInfo<T>)info).Value, typeof(S)),
      };
    }
  }

}